//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <misc.h>
#include <event.h>
#include <ChangeNotifier.h>

CChangeNotifier::CChangeNotifier()
{
    m_pListHead = NULL;
    Ktfs_Init_Lock(&m_lock);
}

CChangeNotifier::~CChangeNotifier()
{
    NotifierListNode *pNotifierNode;
    EventListNode *pHeadEventNode, *pEventNode;

    while (m_pListHead) {
        pNotifierNode = m_pListHead;
        m_pListHead = m_pListHead->pNext;

        pHeadEventNode = pNotifierNode->pEvents;
        while (pHeadEventNode) {
            pEventNode = pHeadEventNode;
            pHeadEventNode = pHeadEventNode->pNext;
            delete pEventNode;
        }

        delete pNotifierNode;
    }

    if (m_lock)
        Ktfs_Term_Lock(&m_lock);
}

/*
 * Add Monitor of specified node
 */

int CChangeNotifier::AddMonitor(struct node *pNode, IEvent** ppRetEvent)
{
    EventListNode *pEventNode = NULL;
    IEvent *pEvent = NULL;
    assert(pNode && ppRetEvent);

    *ppRetEvent = NULL;

    Lock();

    /* Checkint out whether targeted node had been monitered */
    NotifierListNode *pNotifierNode = FindNode(pNode, NULL);
    if (pNotifierNode) {
        pEventNode = new EventListNode;
        if (NULL == pEventNode)
            goto Exit1;

        pEvent = new CEvent(false, 0);
        if (NULL == pEvent)
            goto Exit1;
        /* Both filing system and application who invoke the routine
        * has to hold the reference of event.
        */
        pEvent->AddRef();
        pEvent->AddRef();
        pEventNode->pEvent = pEvent;
        pEventNode->pNext = pNotifierNode->pEvents;
        pNotifierNode->pEvents = pEventNode;
    }
    else {
        pNotifierNode = new NotifierListNode;
        if (NULL == pNotifierNode)
            goto Exit2;

        pEventNode = new EventListNode;
        if (NULL == pEventNode)
            goto Exit2;

        pEvent = new CEvent(false, 0);
        if (NULL == pEvent)
            goto Exit2;

        pEvent->AddRef();
        pEvent->AddRef();
        pEventNode->pEvent = pEvent;
        pEventNode->pNext = NULL;
        pNotifierNode->pNode = pNode;
        pNotifierNode->pEvents = pEventNode;
        pNotifierNode->pNext = m_pListHead;
        m_pListHead = pNotifierNode;
    }
    Unlock();
    *ppRetEvent = pEvent;
    return 0;

Exit1:
    Unlock();
    if (pEventNode)
        delete pEventNode;
    return ENOMEM;

Exit2:
    Unlock();
    if (pNotifierNode)
        delete pNotifierNode;
    if (pEventNode)
        delete pEventNode;
    return ENOMEM;
}

/*
 * Remove specified monitor for specified node, if no monitor for specified
 * node or no match event for specified node then return -1.
 */

int CChangeNotifier::RemoveMonitor(struct node *pNode, IEvent *pEvent)
{
    NotifierListNode *pNotifierNode, *pPreNotifierNode;
    EventListNode *pEventNode, *pPreEventNode;

    assert(pNode && pEvent);

    Lock();

    pNotifierNode = FindNode(pNode, &pPreNotifierNode);
    if (NULL == pNotifierNode)
        goto NoFind;

    pEventNode = FindEvent(pNotifierNode, pEvent, &pPreEventNode);
    if (NULL == pEventNode)
        goto NoFind;

    /* Pick out the event node from event list */
    if (NULL == pPreEventNode)
        pNotifierNode->pEvents = pEventNode->pNext;
    else
        pPreEventNode->pNext = pEventNode->pNext;

    pEventNode->pEvent->Release();
    delete pEventNode;

    /* Remove the notifier node from notifier node list if no more event
     * in this node.
     */
    if (NULL == pNotifierNode->pEvents) {
        if (NULL == pPreNotifierNode)
            m_pListHead = pNotifierNode->pNext;
        else
            pPreNotifierNode->pNext = pNotifierNode->pNext;

        delete pNotifierNode;
    }
    Unlock();
    return 0;

NoFind:
    Unlock();
    return EINVAL;
}

int CChangeNotifier::NotifyMonitor(struct node * pChangeNode)
{
    NotifierListNode *pNotifierNode;
    EventListNode *pEventNode;
    ECode ec;

    Lock();

    if (NULL == m_pListHead)
        goto Exit;

    pNotifierNode = FindNode(pChangeNode, NULL);
    if (NULL == pNotifierNode)
        goto Exit;

    pEventNode = pNotifierNode->pEvents;
    assert(pEventNode != NULL);

    while (pEventNode) {
        ec = pEventNode->pEvent->Notify(1);
        if (FAILED(ec))
            goto Exit;
        pEventNode = pEventNode->pNext;
    }

    Unlock();
    return 0;

Exit:
    Unlock();
    return -1;
}

/*
 * Find the specified notifierNode in notifierNodeList.
 * return notifierListNode point on success, NULL on no found.
 */
NotifierListNode * CChangeNotifier::FindNode(struct node *pNode, NotifierListNode **ppPreNode)
{
    if (NULL == pNode)
        return NULL;

    if (NULL == m_pListHead)
        return NULL;

    NotifierListNode *pNotifierNode, *pPreNode;

    if (m_pListHead->pNode == pNode) {
        pPreNode = NULL;
        pNotifierNode = m_pListHead;
    }
    else {
        pPreNode = m_pListHead;
        while (pPreNode->pNext) {
            if (pPreNode->pNext->pNode == pNode)
                break;
            pPreNode = pPreNode->pNext;
        }
        if (NULL == pPreNode->pNext)
            return NULL;
        else
            pNotifierNode = pPreNode->pNext;
    }

    if (ppPreNode)
        *ppPreNode = pPreNode;
    assert(pNotifierNode->pNode == pNode);
    return pNotifierNode;
}

/*
 * Find the specified eventNode.
 */
EventListNode *CChangeNotifier::FindEvent(NotifierListNode *pNotifierNode,
    IEvent *pEvent, EventListNode **ppPreNode)
{
    if ((NULL == pNotifierNode) || (NULL == pNotifierNode->pEvents))
        return NULL;

    EventListNode *pEventNode, *pPreEventNode;

    if (pNotifierNode->pEvents->pEvent == pEvent) {
        pPreEventNode = NULL;
        pEventNode = pNotifierNode->pEvents;
    }
    else {
        pPreEventNode = pNotifierNode->pEvents;
        while (pPreEventNode->pNext) {
            if (pPreEventNode->pNext->pEvent == pEvent)
                break;
            pPreEventNode = pPreEventNode->pNext;
        }

        if (NULL == pPreEventNode->pNext)
            return NULL;
        else
            pEventNode = pPreEventNode->pNext;
    }

    if (ppPreNode)
        *ppPreNode = pPreEventNode;
    assert(pEventNode->pEvent == pEvent);
    return pEventNode;
}

/*
 * Notify all events whose related node is dirty
 */
int CChangeNotifier::NotifyAllDirty()
{
    NotifierListNode *pNotifierNode;
    EventListNode *pEventNode;

    Lock();

    pNotifierNode = m_pListHead;

    while (pNotifierNode) {
        if (pNotifierNode->pNode->n_flags & N_DIRTY) {
            pEventNode = pNotifierNode->pEvents;
            assert(pEventNode);
            while (pEventNode) {
                assert(pEventNode->pEvent);
                pEventNode->pEvent->Notify(1);
                pEventNode = pEventNode->pNext;
            }
            pNotifierNode->pNode->n_flags &= ~N_DIRTY;
        }
        pNotifierNode = pNotifierNode->pNext;
    }

    Unlock();
    return 0;
}

void CChangeNotifier::Lock()
{
    Ktfs_Lock(&m_lock);
}

void CChangeNotifier::Unlock()
{
    Ktfs_Unlock(&m_lock);
}
